/**
 * 创建二维数组的辅助函数
 * @param {number} cols - 列数
 * @param {number} rows - 行数
 * @returns {Array} 二维数组
 */
function make2DArray(cols, rows) {
  const arr = new Array(cols)
  for (let i = 0; i < arr.length; ++i) {
    arr[i] = new Array(rows)
    for (let j = 0; j < arr[i].length; ++j) {
      arr[i][j] = 0
    }
  }
  return arr
}

let grid // 存储沙粒状态的二维网格
let w = 8 // 每个沙粒的像素大小
let cols, rows // 网格的列数和行数
let hueValue = 200 // 当前沙粒的颜色色相值

/**
 * 检查列索引是否在有效范围内
 * @param {number} i - 列索引
 * @returns {boolean} 是否在有效范围内
 */
function withinCols(i) {
  return i >= 0 && i < cols
}

/**
 * 检查行索引是否在有效范围内
 * @param {number} j - 行索引
 * @returns {boolean} 是否在有效范围内
 */
function withinRows(j) {
  return j >= 0 && j < rows
}

/**
 * p5.js 初始化函数
 * 设置画布和颜色模式
 */
function setup() {
  createCanvas(windowWidth, windowHeight)
  colorMode(HSB, 360, 255, 255) // 使用HSB颜色模式，色相范围0-360，饱和度和亮度范围0-255
  initializeGrid()
}

/**
 * 初始化网格
 * 根据画布大小和沙粒大小计算网格尺寸
 */
function initializeGrid() {
  cols = floor(width / w)
  rows = floor(height / w)
  grid = make2DArray(cols, rows)
}

/**
 * 窗口大小调整时的处理函数
 * 重新计算网格尺寸并保持现有沙粒状态
 */
function windowResized() {
  resizeCanvas(windowWidth, windowHeight)
  let oldGrid = grid
  let oldCols = cols
  let oldRows = rows

  // 重新初始化网格
  initializeGrid()

  // 保持现有沙子的状态
  for (let i = 0; i < min(oldCols, cols); i++) {
    for (let j = 0; j < min(oldRows, rows); j++) {
      grid[i][j] = oldGrid[i][j]
    }
  }
}

/**
 * 鼠标拖拽处理函数
 * 在鼠标位置生成沙粒
 */
function mouseDragged() {
  const mouseCol = floor(mouseX / w) // 计算鼠标所在的列
  const mouseRow = floor(mouseY / w) // 计算鼠标所在的行
  let matrix = 5 // 影响区域的矩阵大小
  let extent = floor(matrix / 2) // 影响区域的半径

  // 在鼠标周围区域生成沙粒
  for (let i = -extent; i <= extent; ++i) {
    for (let j = -extent; j <= extent; ++j) {
      // 75%概率生成沙粒，增加随机性
      if (random(1) < 0.75) {
        let col = mouseCol + i
        let row = mouseRow + j
        // 检查是否在网格范围内
        if (withinCols(col) && withinRows(row)) {
          grid[col][row] = hueValue // 设置沙粒颜色
        }
      }
    }
  }

  // 改变下一个沙粒的颜色（色相值）
  hueValue += 1
  if (hueValue > 360) {
    hueValue = 1
  }
}

/**
 * p5.js 主绘制循环
 * 处理沙粒的物理效果和渲染
 */
function draw() {
  background(0) // 清空画布为黑色

  // 渲染所有沙粒
  for (let i = 0; i < cols; ++i) {
    for (let j = 0; j < rows; ++j) {
      noStroke()
      if (grid[i][j] > 0) {
        fill(grid[i][j], 255, 255) // 根据存储的色相值设置颜色
        let x = i * w
        let y = j * w
        square(x, y, w) // 绘制正方形沙粒
      }
    }
  }

  // 创建下一帧的网格状态
  let nextGrid = make2DArray(cols, rows)

  // 更新沙粒的物理状态（下落）
  for (let i = 0; i < cols; ++i) {
    for (let j = 0; j < rows; ++j) {
      let state = grid[i][j] // 当前位置的状态（0表示空，其他值表示有沙粒）

      // 如果当前位置有沙粒，则计算其下落行为
      if (state > 0) {
        let below = grid[i][j + 1] // 正下方的位置状态

        // 随机选择下落方向（左或右）
        let dir = 1
        if (random(1) < 0.5) {
          dir *= -1
        }

        // 检查斜下方两个位置的状态
        let belowA = -1 // 右下方（或左下方，取决于dir）
        let belowB = -1 // 左下方（或右下方，取决于dir）

        if (withinCols(i + dir)) {
          belowA = grid[i + dir][j + 1]
        }
        if (withinCols(i - dir)) {
          belowB = grid[i - dir][j + 1]
        }

        // 根据周围空间决定沙粒移动方向
        if (below === 0) {
          // 正下方为空，直接下落
          nextGrid[i][j + 1] = state
        } else if (belowA === 0) {
          // 斜下方A为空，向该方向下落
          nextGrid[i + dir][j + 1] = state
        } else if (belowB === 0) {
          // 斜下方B为空，向该方向下落
          nextGrid[i - dir][j + 1] = state
        } else {
          // 无法下落，保持原位
          nextGrid[i][j] = state
        }
      }
    }
  }

  // 更新网格状态
  grid = nextGrid
}
